package net.xdclass.service.impl;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import io.jsonwebtoken.Claims;
import lombok.extern.slf4j.Slf4j;
import net.xdclass.entity.User;
import net.xdclass.enums.BizCodeEnum;
import net.xdclass.enums.SendCodeEnum;
import net.xdclass.feign.CouponFeignService;
import net.xdclass.interceptor.LoginInterceptor;
import net.xdclass.mapper.UserMapper;
import net.xdclass.model.FlushToken;
import net.xdclass.model.LoginUser;
import net.xdclass.request.NewUserRequest;
import net.xdclass.request.UserLoginRequest;
import net.xdclass.request.UserRegisterRequest;
import net.xdclass.service.IUserService;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;
import net.xdclass.service.NotifyService;
import net.xdclass.utils.CommonUtil;
import net.xdclass.utils.JWTUtil;
import net.xdclass.utils.JsonData;
import net.xdclass.vo.UserVO;
import org.apache.commons.codec.digest.Md5Crypt;
import org.apache.commons.lang3.StringUtils;
import org.springframework.beans.BeanUtils;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Propagation;
import org.springframework.transaction.annotation.Transactional;
import springfox.documentation.spring.web.json.Json;

import java.time.LocalDateTime;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

/**
 * <p>
 *  服务实现类
 *
 * </p>
 *
 * @author lixin
 * @since 2024-08-14
 */
@Service
@Slf4j
public class UserServiceImpl extends ServiceImpl<UserMapper, User> implements IUserService {

    @Autowired
    private CouponFeignService couponFeignService;

    @Autowired
    private NotifyService notifyService;

    @Autowired
    private UserMapper userMapper;

    @Autowired
    private StringRedisTemplate redisTemplate;

    /**
     * refresToken过期时间 30天
     */
    private static final long REFRESTOKEN_EXPIRE = 1000 * 60 * 60 * 24 * 15;

    /**
     * 用户注册
     * 邮箱验证码验证
     * 密码加密
     * 账号唯一性检查
     * 插入数据库
     * 新注册用户福利发放
     * @param registerRequest
     * @return
     */
//    @GlobalTransactional
    @Transactional(rollbackFor = Exception.class, propagation = Propagation.REQUIRED)
    @Override
    public JsonData register(UserRegisterRequest registerRequest) {

        boolean checkCode = false;
        if (StringUtils.isNotBlank(registerRequest.getMail())){
            checkCode = notifyService.checkCode(SendCodeEnum.USER_REGISTER, registerRequest.getMail(), registerRequest.getCode());
        }

        if (!checkCode){
            return JsonData.buildResult(BizCodeEnum.CODE_ERROR);
        }

        User user = new User();
        BeanUtils.copyProperties(registerRequest, user);
        user.setCreateTime(LocalDateTime.now());
        user.setSlogan("人生需要动态规划, 学习需要贪心算法");
        //设置密码
        //生成秘钥 盐
        user.setSecret("$1$" + CommonUtil.getStringNumRandom(8));
        //密码+盐处理
        String cryptPwd = Md5Crypt.md5Crypt(registerRequest.getPwd().getBytes(), user.getSecret());
        user.setPwd(cryptPwd);
        //账号唯一性检查 TODO
        if (checkUnique(user.getMail())){
            int rows = userMapper.insert(user);
            log.info("rows={}, 注册成功", rows);

            //新用户注册成功, 初始化信息, 发放福利 TODO
            userRegisterInitTask(user);

//            //模拟异常
//            int a = 1 / 0;

            return JsonData.buildSuccess();
        }else {
            return JsonData.buildResult(BizCodeEnum.ACCOUNT_REPEAT);
        }
    }

    /**
     * 用户登录
     * 根据mail找有没有这条记录
     * 有用密钥+密码加密，进行匹配
     * @param userLoginRequest
     * @return
     */
    @Override
    public JsonData login(UserLoginRequest userLoginRequest) {
        User user = userMapper.selectOne(new QueryWrapper<User>().eq("mail", userLoginRequest.getMail()));
        if (user != null){
            //已注册
            String cryptPwd = Md5Crypt.md5Crypt(userLoginRequest.getPwd().getBytes(), user.getSecret());
            if (cryptPwd.equals(user.getPwd())){
                //登录成功,生成token
                LoginUser loginUser = new LoginUser();
                BeanUtils.copyProperties(user, loginUser);
                Map<String, String> map = JWTUtil.geneJsonWebToken(loginUser);
                //accesstoken
                String accessToken = map.get("token");
                //accesstoken的过期时间
                Long expire = Long.valueOf(map.get("EXPIRE"));
                //UUID生成一个token, 刷新令牌存入redis,设置过期时间 30天
                String refreshToken = CommonUtil.generateUUID();
                redisTemplate.opsForValue().set(refreshToken, "1", REFRESTOKEN_EXPIRE, TimeUnit.MILLISECONDS);

                FlushToken flushToken = new FlushToken();
                flushToken.setAccessToken(accessToken);
                flushToken.setRefresToken(refreshToken);
                flushToken.setEXPIRE(expire);

                //返回accessTokne,refresToken,expire
                Map<String, FlushToken> tokenMap = new HashMap<>();
                tokenMap.put("flushToken", flushToken);

                return JsonData.buildSuccess(tokenMap);
            }else {
                //登录失败
                return JsonData.buildResult(BizCodeEnum.ACCOUNT_PWD_ERROR);
            }

        }else {
            //账号未注册
            return JsonData.buildResult(BizCodeEnum.ACCOUNT_PWD_ERROR);
        }
    }

    /**
     * token无感知刷新
     * @param param
     * @return
     */
    @Override
    public JsonData flushToken(Map<String, FlushToken> param) {
        //先在redis找refreshToken是否存在
        FlushToken flushToken = param.get("flushToken");
        String refreshToken = redisTemplate.opsForValue().get(flushToken.getRefresToken());
        //存在，解密accessToken
        //重新调用JWTUtil方法生成新的accessToken
        //重新生成refreshToken并存进redis，设置过期时间30天
        //返回accessTokne,refreshToken, accessToken的过期时间
        if (refreshToken != null){
            redisTemplate.delete(refreshToken);

            Claims claims = JWTUtil.checkJWT(flushToken.getAccessToken());
            Integer id = claims.get("id", Integer.class);
            LoginUser loginUser = new LoginUser();
            loginUser.setId(Long.valueOf(id));
            loginUser.setName(claims.get("name", String.class));
            loginUser.setHeadImg(claims.get("head_img", String.class));
            loginUser.setMail(claims.get("mail", String.class));
            Map<String, String> map = JWTUtil.geneJsonWebToken(loginUser);

            String newRefreshToken = CommonUtil.generateUUID();
            redisTemplate.opsForValue().set(newRefreshToken, "1", REFRESTOKEN_EXPIRE, TimeUnit.MILLISECONDS);

            FlushToken newFlushToken = new FlushToken();
            newFlushToken.setEXPIRE(Long.valueOf(map.get("EXPIRE")));
            newFlushToken.setAccessToken(map.get("token"));
            newFlushToken.setRefresToken(refreshToken);

            Map<String, Object> tokenMap = new HashMap<>();
            tokenMap.put("flushToken", newFlushToken);

            return JsonData.buildSuccess(tokenMap);
        }else {
            return JsonData.buildResult(BizCodeEnum.ACCOUNT_UNLOGIN);
        }
    }

    /**
     * 查看用户个人信息
     * @return
     */
    @Override
    public UserVO indUserDetail() {
        LoginUser loginUser = LoginInterceptor.threadLocal.get();
        User user = userMapper.selectOne(new QueryWrapper<User>().eq("id", loginUser.getId()));

        UserVO userVO = new UserVO();
        BeanUtils.copyProperties(user, userVO);
        return userVO;
    }

    /**
     * 校验账号唯一
     * @param mail
     * @return
     */
    private boolean checkUnique(String mail) {

        QueryWrapper<User> queryWrapper = new QueryWrapper<User>().eq("mail", mail);

        List<User> list = userMapper.selectList(queryWrapper);
        return list.size() > 0 ? false : true;
    }

    /**
     * 用户初始化，福利信息 TODO
     * @param user
     */
    private void userRegisterInitTask(User user){

        NewUserRequest request = new NewUserRequest();
        request.setUserId(user.getId());
        request.setName(user.getName());
        JsonData jsonData = couponFeignService.addNewUserCoupon(request);

        if(jsonData.getCode() != 0){
            throw new RuntimeException("优惠券发放失败");
        }

        log.info("发放新用户注册优惠券: {}, 结果: {}", request.toString(), jsonData.toString());
    }
}
